package net.qiqbframework.config;


import lombok.extern.slf4j.Slf4j;
import net.qiqbframework.common.Registration;
import net.qiqbframework.common.caching.Cache;
import net.qiqbframework.common.caching.WeakReferenceCache;
import net.qiqbframework.common.lock.LockFactory;
import net.qiqbframework.common.lock.PessimisticLockFactory;
import net.qiqbframework.lifecycle.Phase;
import net.qiqbframework.modelling.command.*;
import net.qiqbframework.modelling.domain.*;
import net.qiqbframework.modelling.repository.HandlerRepository;
import net.qiqbframework.modelling.repository.Repository;
import net.qiqbframework.serialization.Serializer;

import java.util.*;
import java.util.function.Function;


/**
 * 配置聚合运行所需的所有组件
 */
@Slf4j
public class AggregateConfigurer<A> implements AggregateConfiguration<A> {

    private final Class<A> aggregate;

    private final ComponentHolder<AggregateAnnotationCommandHandler<A>> commandHandler;

    private final ComponentHolder<Serializer> aggregateRootSerializer;

    private final ComponentHolder<Repository<A>> repository;

    private final ComponentHolder<Cache> cache;

    private final ComponentHolder<AggregateFactory<A>> aggregateFactory;

    private final ComponentHolder<LockFactory> lockFactory;

    private final ComponentHolder<CommandTargetResolver> commandTargetResolver;

    private final ComponentHolder<AggregateModel<A>> aggregateModel;

    private final ComponentHolder<Boolean> filterEventsByType;
    private final Set<Class<? extends A>> subtypes = new HashSet<>();
    private final List<Registration> registrations = new ArrayList<>();

    private Configuration parent;

    public static <A> AggregateConfigurer<A> defaultConfiguration(Class<A> aggregateType) {
        return new AggregateConfigurer<>(aggregateType);
    }

    protected AggregateConfigurer(Class<A> aggregate) {
        this.aggregate = aggregate;

        aggregateModel = new ComponentHolder<>(() -> parent, name("aggregateMetaModel"),
                c -> c.getComponent(
                        AggregateModelFactory.class,
                        () -> new AnnotatedAggregateModelFactory(c.parameterResolverFactory(),
                                c.handlerDefinition(aggregate), c.javersBuilder())
                ).createModel(aggregate, subtypes));
        commandTargetResolver = new ComponentHolder<>(
                () -> parent, name("commandTargetResolver"),
                c -> c.getComponent(
                        CommandTargetResolver.class,
                        () -> AnnotationCommandTargetResolver.builder().build()
                )
        );

        lockFactory = new ComponentHolder<>(() -> parent, name("lockFactory"), c -> PessimisticLockFactory.usingDefaults());

        aggregateFactory = new ComponentHolder<>(() -> parent, name("aggregateFactory"),
                c -> new GenericAggregateFactory<>(aggregateModel.get()));
        cache = new ComponentHolder<>(() -> parent, name("aggregateCache"), c -> null);

        filterEventsByType = new ComponentHolder<>(() -> parent, name("filterByAggregateType"), c -> false);


        aggregateRootSerializer = new ComponentHolder<>(
                () -> parent, name("aggregateRootSerializer"),
                Configuration::serializer);
        repository = new ComponentHolder<>(
                () -> parent, name("Repository"),
                c -> {
                    HandlerRepository.Builder<A> builder = HandlerRepository.builder(aggregateModel.get());
                    builder.eventStorageEngine(c.eventStorageEngine());
                    builder.aggregateLoadBus(c.loadBus());
                    builder.aggregatePersistBus(c.persistBus());

                    builder.aggregateRootSerializer(aggregateRootSerializer.get());
                    builder.configuration(c);

                    return builder.build();
                });
        commandHandler = new ComponentHolder<>(
                () -> parent, name("aggregateCommandHandler"),
                c -> AggregateAnnotationCommandHandlerBuilder.<A>builder()
                        .repository(repository.get())
                        .javersBuilder(c.javersBuilder())
                        .commandTargetResolver(commandTargetResolver.get())
                        .aggregateModel(aggregateModel.get())
                        .addAggregateHandlingMonitor(c.getComponent(AggregateHandlingMonitor.class))
                        .aggregateRootSerializer(c.aggregateSerializer())
                        .build()
        );


    }


    private String name(String prefix) {
        return prefix + "<" + aggregate.getSimpleName() + ">";
    }


    public AggregateConfigurer<A> configureRepository(Function<Configuration, Repository<A>> repositoryBuilder) {
        repository.update(repositoryBuilder);
        return this;
    }


    public AggregateConfigurer<A> configureAggregateFactory(Function<Configuration, AggregateFactory<A>> aggregateFactoryBuilder) {
        aggregateFactory.update(aggregateFactoryBuilder);
        return this;
    }

    public AggregateConfigurer<A> configureLockFactory(Function<Configuration, LockFactory> lockFactory) {
        this.lockFactory.update(lockFactory);
        return this;
    }


    public AggregateConfigurer<A> configureCommandHandler(
            Function<Configuration, AggregateAnnotationCommandHandler<A>> aggregateCommandHandlerBuilder) {
        commandHandler.update(aggregateCommandHandlerBuilder);
        return this;
    }


    public AggregateConfigurer<A> configureCommandTargetResolver(
            Function<Configuration, CommandTargetResolver> commandTargetResolverBuilder) {
        commandTargetResolver.update(commandTargetResolverBuilder);
        return this;
    }

    public AggregateConfigurer<A> configureCache(Function<Configuration, Cache> cache) {
        this.cache.update(cache);
        return this;
    }


    public AggregateConfigurer<A> configureWeakReferenceCache() {
        return configureCache(c -> new WeakReferenceCache());
    }


    public AggregateConfigurer<A> configureFilterEventsByType(
            Function<Configuration, Boolean> filterConfigurationPredicate) {
        this.filterEventsByType.update(filterConfigurationPredicate);
        return this;
    }

    @Override
    public void initialize(Configuration config) {
        parent = config;
        parent.onStart(
                Phase.LOCAL_MESSAGE_HANDLER_REGISTRATIONS,
                () -> {
                    registrations.add(commandHandler.get().subscribe(parent.commandBus()));
                }
        );
        parent.onShutdown(
                Phase.LOCAL_MESSAGE_HANDLER_REGISTRATIONS,
                () -> {
                    registrations.forEach(Registration::cancel);
                    registrations.clear();
                }
        );


    }

    @Override
    public Repository<A> repository() {

        return repository.get();
    }

    @Override
    public AggregateModel<A> aggregateModel() {
        return this.aggregateModel.get();
    }


    @Override
    public Class<A> aggregateType() {
        return aggregate;
    }

    @Override
    public AggregateFactory<A> aggregateFactory() {
        return aggregateFactory.get();
    }


    public AggregateConfigurer<A> withSubtypes(Collection<Class<? extends A>> subtypes) {
        this.subtypes.addAll(subtypes);
        return this;
    }
}
